﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace SolutionOrchestrator
{
	public class ObservableObject : INotifyPropertyChanged
	{
		static ObservableObject()
		{
			_deferredInvoker.Name = "ObservableObject Deferred Invoker";
			_deferredInvoker.IsBackground = true;
			_deferredInvoker.Start();
		}

		protected void Subscribe(object obj)
		{
			var npc = obj as INotifyPropertyChanged;
			if (npc != null)
			{
				npc.PropertyChanged += NpcPropertyChanged;
			}
		}

		protected void Unsubscribe(object obj)
		{
			var npc = obj as INotifyPropertyChanged;
			if (npc != null)
			{
				npc.PropertyChanged -= NpcPropertyChanged;
			}
		}

		protected virtual void NpcPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
		{

		}

		public event PropertyChangedEventHandler PropertyChanged;

		protected virtual void OnPropertyChanged(string name)
		{
			var handler = PropertyChanged;
			if (handler != null)
			{
//				var ea = new PropertyChangedEventArgs(name);
//				var disp = Application.Current.Dispatcher;
//				if (Dispatcher.CurrentDispatcher != disp)
//				{
					lock (_invalidatedSchedule)
					{
						_invalidatedSchedule[new PoolKey(this, name)] = new PoolValue(handler);
						if (_invalidatedSchedule.Count < 16)
						{
							_deferredInvokerPing.Set();
						}
					}
//				}
//				else
//				{
//					handler(this, ea);
//				}
			}
		}

		class PoolKey : IEquatable<PoolKey>
		{
			public readonly object Instance;
			public readonly string PropertyName;

			public PoolKey(object instance, string propertyName)
			{
				Instance = instance;
				PropertyName = propertyName;
			}

			public bool Equals(PoolKey other)
			{
				if (ReferenceEquals(null, other))
				{
					return false;
				}
				if (ReferenceEquals(this, other))
				{
					return true;
				}
				return Equals(other.Instance, Instance) && Equals(other.PropertyName, PropertyName);
			}

			public override bool Equals(object obj)
			{
				return Equals(obj as PoolKey);
			}

			public override int GetHashCode()
			{
				unchecked
				{
					return ((Instance != null ? Instance.GetHashCode() : 0) * 397) ^ (PropertyName != null ? PropertyName.GetHashCode() : 0);
				}
			}
		}

		public class PoolValue
		{
			public PoolValue(PropertyChangedEventHandler handler)
			{
				Handler = handler;
			}

			public readonly PropertyChangedEventHandler Handler;
		}

		static readonly Dictionary<PoolKey, PoolValue> _invalidatedSchedule = new Dictionary<PoolKey, PoolValue>();

		static readonly Thread _deferredInvoker = new Thread(DeferredInvokerWorker);
		static readonly AutoResetEvent _deferredInvokerPing = new AutoResetEvent(false);

		static void DeferredInvokerWorker()
		{
			var disp = Application.Current.Dispatcher;
			while (true)
			{
				_deferredInvokerPing.WaitOne(2000);

				KeyValuePair<PoolKey, PoolValue>[] schedule;
				lock (_invalidatedSchedule)
				{
					schedule = _invalidatedSchedule.ToArray();
					_invalidatedSchedule.Clear();
				}
				int byN = 32;
				for (int i = 0; i < schedule.Length; i += byN)
				{
					var count = Math.Min(schedule.Length - i, byN);
					var group = new KeyValuePair<PoolKey, PoolValue>[count];
					Array.Copy(schedule, i, group, 0, count);

					unchecked
					{
						_schedules++;
					}
					Trace.WriteLine("Invoke: " + _schedules);
					disp.Invoke(new Action<KeyValuePair<PoolKey, PoolValue>[]>(x =>
					{
						foreach (var item in x)
						{
							item.Value.Handler(item.Key.Instance, new PropertyChangedEventArgs(item.Key.PropertyName));
						}
					}), group);
				}
			}
		}

		static long _schedules;
	}
}